在這一篇以及之後的幾篇,我們會把心力著重在網頁後端的部分。為了避免你忘記網頁後端到底在幹嘛,先讓你回顧一下之前看過的那張皇輿全覽圖
好,接著用一個早餐店的例子來讓你了解網頁後端到底是在做什麼。其實早餐店店員跟後端伺服器在做的事情有 87 成像。
假設你是一個很菜的早餐店店員好了,可能會發生類似的情境:
仔細觀察你會發現,上面這兩種情境都符合一樣的流程:
在網頁的世界裡,客人就是使用者(或者說是瀏覽器,也叫 client 端),店員就是 Server、網頁後端。而客人點餐的這個動作就叫做 Request,店員回覆就叫做 Response。
我們來看看真實網頁世界的 Request 長怎樣:
GET /users/20091346/articles HTTP/1.1
Host: ithelp.ithome.com.tw
Connection: keep-alive
Pragma: no-cache
Cache-Control: no-cache
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.98 Safari/537.36
雖然看起來都是一些看不懂的英文,但其實重點是前兩行:
GET /users/20091346/articles HTTP/1.1
Host: ithelp.ithome.com.tw
Host
就是主機位置的意思,意思是說你要請求的 Domain 叫做:ithelp.ithome.com.tw
,然後GET
你先不要管他好了,先看後面的/users/20091346/articles
,跟前面的 Host 接起來就是完整的一個 URL 了。後面的 HTTP/1.1
只是在講說你用的版本是什麼。
所以上面那個 Request,其實就是在說:「我要一份 ithelp.ithome.com.tw/users/20091346/articles
,請問有嗎?」
來看一下 Server 回的 Response:
<!DOCTYPE html>
<html lang="zh-TW">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>個人頁 - iT 邦幫忙::一起幫忙解決難題,拯救 IT 人的一天</title>
....(略)
</html>
聰明的你一定看得出來,這個就是我們教過的 HTML。就這樣,因為我要求的網頁存在,於是 Server 就把我想要的東西給我了。
所以 Server 在做什麼?就是看 client 需要什麼,然後把它需要的東西給他。如果找不到他需要的東西,就返回一個錯誤碼跟他說:抱歉我找不到。
之前有跟大家介紹過神器:Chrome dev tool,你可以從上面看到瀏覽器到底發出了哪些 Request
上面顯示 Request 的名稱、Method、Status、Type、Size 跟 Time。名稱、Type、Size 跟 Time 都相對好懂一點,就是要求哪些類型的哪些檔案然後花了多少時間去下載以及檔案大小多大。Method 跟 Status 這個就要再好好講解一下了。
讓我們再回到那個早餐店的例子:
客人:我要一份鮪魚蛋堡,然後不要加番茄,再一杯紅茶中杯的去冰
店員(喊向內場):一份鮪魚蛋堡不番茄、中涼紅
這個例子只是要告訴大家說,在早餐店內部(或者你有做過餐飲業的話)都會有一套自己的代號,來替代一些很長的名字。例如上述的「紅茶中杯去冰」,熟悉的店員就會跟內場喊說:中涼紅,裡面就知道是什麼了。足足把字數少了一半。
這樣雖然看似很足夠了,可是某天這個早餐店老闆突然中了樂透,想要把這一家早餐店擴張全世界!並且要讓所有跨國分公司的早餐店員工都先來台灣受訓,可是那些外國人儘管有一定的中文基礎,但是又講得不太好,如果代號不改的話,就會變成這樣:
客人:我要一份鮪魚蛋堡,然後不要加番茄,再一杯紅茶中杯的去冰
外國人店員:一份威瘀擔保不飯且、重量轟
內場:維大力?
外國人店員:義大利?
怎麼辦呢?聰明的老闆立刻想出了另外一套方法,我用英文代號就好了嘛!於是就自己制定了一套把每個餐點轉成英文代號的系統,然後強迫員工像背單戰那樣背起來。
客人:我要一份鮪魚蛋堡,然後不要加番茄,再一杯紅茶中杯的去冰
外國人店員:TBET, MBI
餐點的部份解決了,但還有一段還沒解決,那就是你點完餐之後,有時候內場會告知你說食材已經沒了、賣完了,要你請客人換一個餐點。簡單來說,內場需要告知在點餐的外場以下幾種情況:
聰明的老闆也立刻就設想到,如果他一樣用中文的話就會造成溝通上的困難,決定統一使用數字!並且把每一個狀態都設定成一個三位數的數字:
200:正常供應,我們正在做了
301:代號變了喔,你怎麼用舊的代號
404:這個餐點沒有賣喔
500:內場出包了,先別煩我!
例如說:
客人:我要一份鮪魚蛋堡
外場:一份鮪魚蛋堡
內場:200
外場(告知客人):好的,我們現在為你做一個準備的動作,等等會用一個通知的動作告知你。
客人:我要一份蚵仔煎
外場:一份蚵仔煎
內場:404
外場:不好意思,這個沒賣喔,你要不要問問看對面的 711?那邊的店員最厲害了,他們可能有賣。
好了,內外場的基本溝通也解決了,一切看似皆大歡喜,現在可以點餐、可以溝通內外場狀況;但還少了一樣最重要的小事,那就是從澳洲來的客人常常會有一些出人意表的需求。例如說在你餐點快做好的時候跟你說我不要了,或是在你做一半的時候跟你說:我還是改成不要加蛋好了,諸如此類的。為了應付這些突然其來的事情,我們需要另外一套符號系統。聰明的老闆用了一天的時間就規劃好了,他定義了幾種動作:
客人:我的餐到底好了沒啊!
外場:GET MIKE
內場:MIKE 的訂單還要五分鐘喔
客人:可以幫我加一杯賴茶嗎
外場:PATCH 賴茶
內場:(為保護當事人,LINE ID 已做馬賽克處理)
客人:剛剛是另外一個我點的,不是現在的我。我不要了!
外場:DELETE
內場:OK 好
自從有了這一整套完善的系統之後,這一間早餐店就越做越大,甚至還有可能在美國納斯達克上市!而老闆還被奉為管理之神,許多記者紛紛想要採訪到底要怎麼樣才能夠管理這樣的一個跨國企業,結果老闆只淡淡的說了一句:
你們啊,去學網頁設計吧!
好,老闆的這一句話把我們再度拉回來今天的主題,抱歉讓你們看了上面那麼長一段廢文,但這一切都只是為了鋪梗。還記得我們一開始要講的是 Method 跟 Status 這兩個東西嗎?
其實 Method 就是上面外場跟內場溝通的那些動作啦,有 87 成像。例如說你今天需要跟 server 拿一個檔案,你就GET 檔案路徑
,Server 就會給你這個檔案了。或者你用POST name=peter
,就代表說你要向伺服器提交這個資料。
可是在返回 Response 的時候還會多附帶一個資訊:Status,就是上面早餐店的那些數字代碼。例如說回你 200
代表:有喔,這個檔案我有。回你404
代表 not found
,這個檔案我找不到。
這些 HTTP 的狀態碼跟 Method 都有詳細定義的,你們可以去維基百科上面稍微查查看,就知道我所言不假。
所以囉,Status 跟 Method 這兩個東西就是為了方便 Client 跟 Server 可以溝通,由人定義出來的一套數字/符號系統。只要你有認真看我上面寫的早餐店的例子,你就會明白為什麼要這樣設定。
既然我們已經懂原理了,就可以用 Node.js 提供的 API 寫一個非常非常簡單的 Server,讓我們開始吧!(儘管我前面一直說要寫 ES6,但是當你懶得設置環境的時候,還是可以偷懶寫一下 ES5)
先建立一個server.js
var http = require("http");
http.createServer(function(request, response) {
response.writeHead(200, {"Content-Type": "text/plain"});
response.write("Hello World");
response.end();
}).listen(8888);
接著node server.js
,執行之後去瀏覽器打開http://localhost:8888
,就會發現它輸出Hello World
了!
然後回到你的 terminal,按下Ctrl+C
把程式停止
上面那段程式就是靠 Node.js 提供的 API 幫我們建立一個很簡單的 http 伺服器,可以自訂一個 function 去接收 request 以及輸出 response。我們可以再來挑戰更進階一點點的。
var http = require("http");
http.createServer(function(request, response) {
var url = request.url;
var method = request.method;
console.log(method, url);
// 這個 200 就是 http status 200,代表成功的意思
// cotent type 是指回應的內容是純文字
response.writeHead(200, {"Content-Type": "text/plain"});
if (url === '/hello') {
response.write("哈囉");
} else {
response.write('hey~~');
}
response.end();
}).listen(8888);
執行之後你會發現只有到 http://localhost:8888/hello 的時候會看見哈囉
,其他網址都只能看到hey~~
。但如果一個簡單的伺服器只能輸出文字不是太遜了嗎?我們來試試看輸出網頁吧!
var http = require("http");
http.createServer(function(request, response) {
var url = request.url;
var method = request.method;
console.log(method, url);
if (url === '/page1') {
response.writeHead(200, {"Content-Type": "text/html"});
response.write(
"<!DOCTYPE html>" +
"<html>" +
"<body>" +
"<h1>page1</h1>" +
"</body>" +
"</html>"
);
} else if (url === '/page2'){
response.writeHead(200, {"Content-Type": "text/html"});
response.write(
"<!DOCTYPE html>" +
"<html>" +
"<body>" +
"<h1>page2</h1>" +
"</body>" +
"</html>"
);
} else {
response.writeHead(404);
}
response.end();
}).listen(8888);
在上面這個例子中,你會發現只有到/page1
跟/page2
可以看到檔案,其它頁面都會告訴你說頁面不存在,這個就是 HTTP 狀態碼 404
的意思,Page not Found.
最後來一個簡單的範例輸出一些 Request 的資訊:
var http = require("http");
http.createServer(function(request, response) {
var url = request.url;
var method = request.method;
console.log(method, url);
response.writeHead(200, {"Content-Type": "text/html"});
response.write(
"<!DOCTYPE html>" +
"<html>" +
"<body>" +
"<h1>Method: " + method + "</h1>" +
"<h1>Url: " + url + "</h1>" +
"<p>Header: " + JSON.stringify(request.headers) + "</p>" +
"</body>" +
"</html>");
response.end();
}).listen(8888);
如果想研究更多的可以參考:Node入門。
最後總結一下這篇的重點,大概就是讓你知道什麼是 http 伺服器,然後都在負責一些什麼東西。其實就是負責接收 request 然後傳回 response,至於要怎麼處理、傳回哪些資料,這些就是你要動手寫 code 的部分了。如果還是不太懂,再想一下早餐店的例子吧!